<?php
/**
 * 
 * Utility class for parsing data and objects from XML representation
 * 
 * Parsing rules structure
 * configuration 
 * |-open
 * | |-element 
 * |   |- object - method - argsmap
 * |-cdata 
 * | |- method
 * |-close
 *   |- element 
 *      |-object - method 
 * 
 * @package WKernel
 * 
 */
class WK_Markup_Parser {
	private $configuration = null;
	private $parser  = null;	
	private $path    = array(); 
	private $element = null;
	private $result  = null;
	private $markup  = null;
	
	/**
	 * <code>
	 * configuration
	 * |-root - root object
	 * |-open 
	 * | |-[element_name]
	 * |    |-method - method to call on openning of the element
	 * |    |-argmap - mapping element attributes to ,etod arguments
	 * |-close
	 * | |-[element_name]
	 * |    |-method - method to call on closing of the element
	 * |-cdata
	 *   |-[element_name]
	 *      |-method - method to handle the element cdata
	 * </code>
	 * @param string $markup string with XML-data
	 * @param array $configuration parsing rules for given file 
	 */
	public function __construct($markup, $configuration){
		$this->configuration = $configuration;
		$this->markup = $markup;
		$this->path   = array($configuration['root']);
		$this->initParser();
	}
	
	/**
	 * @ignore
	 */
	private function initParser(){
		$this->parser = xml_parser_create('UTF-8');
		xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
		xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE,   1);
		xml_set_object($this->parser, $this);
		xml_set_element_handler($this->parser, 'startElementHdl', 'endElementHdl');
		xml_set_character_data_handler($this->parser, 'cdataHdl');
	}
	
	/**
	 * Execute parsing
	 * @return mixed
	 */
	public function run(){
		xml_parse($this->parser, $this->markup);
		return $this->result;
	}
	
	/**
	 * @ignore
	 * Enter description here ...
	 * @param $parser
	 * @param $name
	 * @param $attributes
	 * @throws Exception
	 */
	public function startElementHdl($parser, $name, $attributes){		
		$argmap = $this->getArgmap('open', $name);
		$params = array();
		if(is_array($argmap)) {
			foreach ($argmap as $atrname) 
				array_push($params, @$attributes[$atrname]);
		}
		
		$obj = $this->call('open', $name, $params);
		
		array_push($this->path, $obj);
		$this->element = $name;
	}
	
	/**
	 * @ignore
	 * Enter description here ...
	 * @param unknown_type $parser
	 * @param unknown_type $name
	 */
	public function endElementHdl($parser, $name){		
		$this->call('close', $name);
		array_pop($this->path);	
		$this->element = $name;		
		if(empty($this->path)) $this->result = $object;
	}
	
	/**
	 * @ignore
	 * Enter description here ...
	 * @param unknown_type $parser
	 * @param unknown_type $data
	 */
	public function cdataHdl($parser, $data){
		$tmp_data = trim($data);
		if(!empty($tmp_data)) {
			$this->call('cdata', $this->element, array($data));
		}
	}
	
	private function call($type, $name, $args = array()){
		$object = end($this->path);
		$method = $this->getMethod($type, $name);
		array_unshift($args, $name);
		if(method_exists($object, $method))
			return call_user_func_array(array($object, $method), $args);
		else
			return $object;
	}
	
	private function getMethod($type, $name){
		$confnode = $this->getConfNode($type, $name);
		return @$confnode['method'];
	}
	
	private function getArgmap($type, $name){
		$confnode = $this->getConfNode($type, $name);
		return @$confnode['argmap'];
	}
	
	private function getConfNode($type, $name){
		if(!isset($this->configuration[$type][$name])) $name = '*';
		return @$this->configuration[$type][$name];
	}
}